package moe.server;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Logger;

import moe.service.AbstractHandler;
import moe.service.CollectionHandler;
import moe.service.CommonHandler;
import moe.service.PoolHandler;
import moe.service.PostHandler;
import moe.service.TagHandler;

import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.MethodNotSupportedException;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestHandler;
import org.apache.http.util.EntityUtils;

public class JsonHandler implements HttpRequestHandler {
	private static final Logger log = Logger.getLogger(JsonHandler.class.getName());

	private static Map<String, AbstractHandler> handlerMap = new HashMap<String, AbstractHandler>();

	static {
		registerHandler(new CommonHandler());
		registerHandler(new PostHandler());
		registerHandler(new PoolHandler());
		registerHandler(new TagHandler());
		registerHandler(new CollectionHandler());
	}

	private static void registerHandler(AbstractHandler obj) {
		Method[] methods = obj.getClass().getDeclaredMethods();
		for (Method method : methods) {
			if (method.getModifiers() != Modifier.PUBLIC) {
				continue;
			}
			Class[] classes = method.getParameterTypes();
			if (classes.length == 2 && classes[0] == HttpContext.class && classes[1] == Map.class) {
				String name = method.getName();
				if (handlerMap.containsKey(name)) {
					log.warning(name + " handler already exist!");
					continue;
				}
				handlerMap.put(name, obj);
			}
		}
	}

	public void handle(HttpRequest request, HttpResponse response, HttpContext context)
			throws HttpException, IOException {
		String method = request.getRequestLine().getMethod().toUpperCase(Locale.ENGLISH);
		if (!method.equals("GET") && !method.equals("POST")) {
			throw new MethodNotSupportedException(method + " method not supported");
		}
		String requestURI = request.getRequestLine().getUri();
		int index = requestURI.indexOf("?");
		String queryString = "";
		if (index != -1) {
			queryString = requestURI.substring(index + 1);
		}

		if (method.equals("POST")) {
			if (request instanceof HttpEntityEnclosingRequest) {
				HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
				queryString += "&" + EntityUtils.toString(entity, "UTF-8");
			}
		}

		try {
			Map<String, String[]> queryMap = parseQueryString(queryString);
			resolveAction(context, queryMap);
		} catch (Exception e) {
			throw new HttpException(e.getMessage(), e);
		}
	}

	private Map<String, String[]> parseQueryString(String queryString) {
		Map<String, String[]> result = new HashMap<String, String[]>();
		if (queryString == null || queryString.equals("")) {
			return result;
		}
		String[] queryArray = queryString.split("&");
		for (String query : queryArray) {
			String[] arr = query.split("=");
			if (arr.length != 2) {
				continue;
			}
			String name = null, value = null;
			try {
				name = URLDecoder.decode(arr[0], "UTF-8");
				value = URLDecoder.decode(arr[1], "UTF-8");
			} catch (UnsupportedEncodingException e) {
				continue;
			}
			if (result.containsKey(name)) {
				String[] src = result.get(name);
				String[] dest = new String[src.length + 1];
				System.arraycopy(src, 0, dest, 0, src.length);
				dest[src.length] = value;
				result.put(name, dest);
			} else {
				result.put(name, new String[] { value });
			}
		}
		return result;
	}

	private void resolveAction(HttpContext context, Map<String, String[]> query) {
		HttpResponse response = (HttpResponse) context.getAttribute(ExecutionContext.HTTP_RESPONSE);
		if (!query.containsKey("action")) {
			response.setStatusCode(HttpStatus.SC_BAD_REQUEST);
			return;
		}
		String action = query.get("action")[0];
		if (!handlerMap.containsKey(action)) {
			response.setStatusCode(HttpStatus.SC_BAD_REQUEST);
			return;
		}
		AbstractHandler handler = handlerMap.get(action);
		try {
			Method method =
					handler.getClass().getDeclaredMethod(action, HttpContext.class, Map.class);
			method.setAccessible(true);
			method.invoke(handler, context, query);
		} catch (Exception e) {
			log.warning("Action: \"" + action + "\", " + e.getCause().getClass().getName() + ": "
					+ e.getCause().getMessage());
			e.printStackTrace();
			response.setStatusCode(HttpStatus.SC_BAD_REQUEST);
			return;
		}
	}
}
